小编典典

git stash 并应用

all

我是 git 新手,不太清楚 stash 的工作原理。

假设我正在处理分支 master 并尝试git pull接收错误,即我的本地更改将被覆盖并且需要隐藏或提交。如果我没有暂存我的任何更改并运行git stash,然后执行git pull并成功更新,当我发生什么git stash apply

一般来说,如果其他人修改文件并且我运行git pull,当我运行时会发生什么run git stash apply?它是否会覆盖刚刚更新的文件,无论它们在我存储它们时是否已上演?它会用git pull隐藏的文件覆盖我刚刚更新的每个文件吗?


阅读 196

收藏
2022-03-01

共1个答案

小编典典

快速“TL;DR”外卖版,以后可以回来学习更多

git stash在当前提交上挂起一个 stash-bag——这是一种特殊的合并提交形式,它不在任何分支上HEAD。稍后git stash apply,当您进行任何提交时(可能是不同的提交),然后尝试通过查看挂起的 stash-bag 和挂起它的提交来恢复 git 计算的更改。

完成更改后,您应该使用git stash drop它从“隐藏”的提交中释放 stash-bag。(而且,git stash pop只是“应用,然后自动删除”的简写。不过,我建议将这两个步骤分开,以防您不喜欢“应用”的结果并且想稍后再试。)

长版

git stash实际上相当复杂。

有人说“一旦你理解了 X,git 就会变得更有意义”,对于“X”的许多不同值,这概括为“一旦你理解了 git,git 就会变得更有意义”。:-)

在这种情况下,要真正理解stash,您需要了解提交、分支、索引/暂存区域、git 的引用名称空间和合并所有工作的方式,因为git stash创建了一个非常特殊的合并提交,它由外部名称引用通常的命名空间——一种奇怪的合并,根本不是“在一个分支上”——并git stash apply使用 git 的合并机制来尝试“重新应用”在进行特殊合并提交时保存的更改,可选地保留区别在分阶段和非分阶段的变化之间。

幸运的是,您实际上不需要了解所有这些内容即可使用 git stash

在这里,您正在处理某个分支 ( master) 并且您有一些尚未准备好的更改,因此您不想在分支上提交它们。1 同时,其他人将一些好的东西——或者至少,你希望它是好的——放到origin/master远程仓库的 over,所以你想把它们捡起来。

假设您和他们都从以 结尾的提交开始- A - B - C,即,C当您开始在分支上工作时,这是您在 repo 中的最终提交master。新的“好东西”提交,我们将调用Dand E

在您的情况下,您正在运行git pull并且由于“工作目录不干净”问题而失败。所以,你跑git stash。这会以其特殊的怪异 stash-y 方式为您提交您的东西,因此您的工作目录现在是干净的。现在你可以了git pull

就提交的绘制而言(使用gitkor得到的图形git log --graph),您现在有了这样的东西。stash 是你运行时在分支i-w中“打开”的提交的小袋子。(名称的原因是这些是存储的“i”索引/暂存区域和“w”ork-tree 部分。)master``git stash``i``w

- A - B - C - D - E      <-- HEAD=master, origin/master
          |\
          i-w            <-- the "stash"

如果您开始工作master并且从未进行任何提交,那么您会得到这张图。您最近的提交是因此C。进行存储后,git pull能够添加提交D并添加E到您的本地分支master。隐藏的工作包仍然挂着C

如果你自己做了一些提交——我们将它们称为Y,用于你的提交,并且Z只是为了有两个提交——“stash then pull”的结果如下所示:

                   .-------- origin/master
- A - B - C - D - E - M  <-- HEAD=master
            \       /
              Y - Z
                  |\
                  i-w    <-- the "stash"

这一次,在stash挂掉它的存储袋之后Zpull——就在fetch那时merge——必须进行真正的合并,而不仅仅是“快进”。所以它使 commit M,合并提交。origin/master标签仍然是指 commit ,而E不是M。您现在master处于 commit状态M,这是Eand的合并Z。你“领先”了origin/master

在任何一种情况下,如果您现在运行git stash applystash 脚本(它是一个使用许多低级 git“管道”命令的 shell 脚本),2会有效地执行以下操作:

git diff stash^ stash > /tmp/patch
git apply /tmp/patch

这个 diffs stash,它将w存储的“工作树”部分命名为正确的3父级。换句话说,它会找出正确的父提交(CZ,视情况而定)和隐藏的工作树之间的“你改变了什么”。然后它将更改应用于当前签出的版本,即EM,再次取决于您的开始位置。

顺便说一句,git stash show -p实际上只是运行相同的git diff命令(当然没有> /tmp/patch部分)。没有-p,它运行 diff --stat。因此,如果您想详细了解git stash apply将合并的内容,请使用git stash show -p. (不过,这不会向您展示git stash apply可以尝试从存储的索引部分应用什么;这是我对存储脚本的一个小抱怨。)


在任何情况下,一旦 stash 干净地应用,您可以使用git stash drop删除对 stash-bag 的引用,以便对其进行垃圾收集。在你放弃它之前refs/stash,它有一个名字(它的名字变成) 并让新的 stash 使用这个名字。大多数 reflog 条目会保留 90 天(您可以将其配置为不同)然后过期。默认情况下,存储不会过期,但如果您以其他方式配置,“推送”存储可能会丢失,因此如果您开始根据自己的喜好配置 git,请小心依赖“永久保存”。stash@{0}``stash``stash@{1}``refs/stash

请注意,git stash drop此处“弹出”存储堆栈,重新编号stash@{2}stash@{1}使其stash@{1}变为 plain stash。用于git stash list查看存储堆栈。


1无论如何都继续提交它们也不错,然后稍后git rebase -i再压缩或修复第二个、第三个、第四个、…、第 n 个提交,和/或重写临时“检查点”提交。但这与此无关。

2它稍微复杂一些,因为您可以--index尝试使用暂存的更改来暂存,但实际上,如果您查看脚本,您会看到实际的命令序列git diff ... | git apply --index。在这种情况下,它确实只是应用了差异!不过,最终它会git merge-recursive直接调用以合并到工作树中,从而允许从其他地方引入相同的更改。git apply如果你的补丁做了一些“好东西”提交D并且E也做了的事情,那么一个普通的将会失败。

3stash这使用了 git 的父命名魔法语法,并在脚本内部进行了一些预先规划。因为 stash 是这个时髦的合并提交,w有两个甚至三个父级,但是 stash 脚本设置它,以便“第一个父级”是原始提交,C或者Z,视情况而定。“第二个父级”stash^2是提交时的索引状态,如i悬挂的小存储袋中所示,“第三个父级”(如果存在)是未暂存且可能被忽略的文件,来自git stash save -ugit stash save -a.

请注意,在此答案中,我假设您没有仔细暂存工作树的一部分,并且您没有使用git stash apply --index它来恢复暂存索引。通过不执行任何这些操作,您会使提交变得非常多余,因此我们在此步骤i中无需担心它。apply如果您正在使用apply --index或等效,并且暂存的项目,您可能会遇到更多的极端情况,其中存储不会干净地应用。

这些相同的警告适用于使用-uor保存的具有-a第三次提交的存储,还有更多的极端情况。

对于这些特别困难的情况,git stash提供了一种将存储转换为成熟分支的方法——但我将把所有这些留给另一个答案。

2022-03-01